package com.gitee.wsl.doc.prosemirror.transform.step

import com.gitee.wsl.doc.prosemirror.exception.RangeError
import com.gitee.wsl.doc.prosemirror.model.Schema
import com.gitee.wsl.doc.prosemirror.model.fragment.Slice
import com.gitee.wsl.doc.prosemirror.model.fragment.ext.mapFragment
import com.gitee.wsl.doc.prosemirror.model.mark.Mark
import com.gitee.wsl.doc.prosemirror.model.node.Node
import com.gitee.wsl.doc.prosemirror.transform.map.Mappable
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.intOrNull
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.json.put
import kotlin.math.max
import kotlin.math.min

// Remove a mark from all inline content between two positions.
class RemoveMarkStep(
    // Create a mark-removing step.
    // The start of the unmarked range.
    override val from: Int,
    // The end of the unmarked range.
    override var to: Int,
    // The mark to remove.
    val mark: Mark
) : Step() {

    override fun apply(doc: Node): StepResult {
        val oldSlice = doc.slice(this.from, this.to)
        val slice = Slice(
            mapFragment(oldSlice.content, { node: Node, parent: Node, i: Int ->
                node.mark(this.mark.removeFromSet(node.marks))
            }, doc),
            oldSlice.openStart,
            oldSlice.openEnd
        )
        return StepResult.fromReplace(doc, this.from, this.to, slice)
    }

    override fun invert(doc: Node): Step {
        return AddMarkStep(this.from, this.to, this.mark)
    }

    override fun map(mapping: Mappable): Step? {
        val from = mapping.mapResult(this.from, 1)
        val to = mapping.mapResult(this.to, -1)
        if (from.deleted && to.deleted || from.pos >= to.pos) return null
        return RemoveMarkStep(from.pos, to.pos, this.mark)
    }

    @Suppress("ComplexCondition")
    override fun merge(other: Step): Step? {
        return if (other is RemoveMarkStep &&
            other.mark == this.mark &&
            this.from <= other.to && this.to >= other.from
        ) {
            RemoveMarkStep(min(this.from, other.from), max(this.to, other.to), this.mark)
        } else {
            null
        }
    }

    override fun toJSON() = buildJsonObject {
        put("stepType", "removeMark")
        put("mark", mark.toJSON())
        put("from", from)
        put("to", to)
    }

    companion object : StepJsonParser<RemoveMarkStep> {
        init {
            jsonID("removeMark", this)
        }

        override fun fromJSON(schema: Schema, json: JsonObject): RemoveMarkStep {
            val from = json.get("from")?.jsonPrimitive?.intOrNull
            val to = json.get("to")?.jsonPrimitive?.intOrNull
            if (from == null || to == null) {
                throw RangeError("Invalid input for RemoveMarkStep.fromJSON")
            }
            return RemoveMarkStep(from, to, schema.markFromJSON(json["mark"]?.jsonObject))
        }
    }
}

fun Step.Companion.removeMark(
    // Create a mark-removing step.
    // The start of the unmarked range.
    from: Int,
    // The end of the unmarked range.
    to: Int,
    // The mark to remove.
    mark: Mark
) = RemoveMarkStep(from,to, mark)